extends layout

block headContent
	title Mempool Summary

block content
	h1.h3 Mempool Summary
	hr

	div#progress-wrapper.mb-huge
		div.card.shadow-sm.mb-3
			div.card-body
				h4.h6 Loading mempool transactions: 
					span(id="progress-text")
				div.progress.mt-2(id="progress-bar", style="height: 7px;")
					div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
	

	div(id="main-content", style="display: none;")
		div.card.shadow-sm.mb-3
			div.card-body.px-2.px-md-3
				h3.h6.mb-0 Summary
				hr

				div.clearfix
					div.row
						div.summary-table-label Tx Count
						div.summary-table-content.text-monospace(id="tx-count")

					div.row
						div.summary-table-label Memory Usage
						div.summary-table-content.text-monospace(id="mem-usage")

					div.row
						div.summary-table-label Total Fees
						div.summary-table-content.text-monospace(id="total-fees")

					div.row
						div.summary-table-label Avg Fee
						div.summary-table-content.text-monospace(id="avg-fee")

					div.row
						div.summary-table-label Avg Fee Rate
						div.summary-table-content.text-monospace(id="avg-fee-rate")

		div#detail-charts-and-data
			div.card.shadow-sm.mb-3
				div.card-body.px-2.px-md-3
					h3.h6.mb-0 Transactions by fee rate
					hr

					canvas.mb-3(id="mempoolBarChart", height="100")

					div.table-responsive
						table.table.table-striped.mb-3
							thead
								tr
									th Fee Rate
									th.text-right Tx Count
									th.text-right Total Fees
									th.text-right Avg Fee
									th.text-right Avg Fee Rate
							tbody(id="fee-rate-table-body")
								tr(id="fee-rate-table-row-prototype", style="display: none;")
									td.text-monospace.data-label
									td.text-monospace.text-right.data-count
									td.text-monospace.text-right.data-total-fees
									td.text-monospace.text-right.data-avg-fee
									td.text-monospace.text-right.data-fee-rate

			div.card.shadow-sm.mb-3
				div.card-body.px-2.px-md-3
					h3.h6.mb-0 Transactions by size
					hr

					canvas.mb-3(id="txSizesBarChart", height="100")

			div.card.shadow-sm.mb-3
				div.card-body.px-2.px-md-3
					h3.h6.mb-0 Transactions by age
					hr

					canvas.mb-3(id="txAgesBarChart", height="100")
		

block endOfBody
	script(src="./js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
	script(src='./js/decimal.js')
	script.
		var txidChunks = !{JSON.stringify(mempooltxidChunks)};
		var satoshiPerByteBucketMaxima = !{JSON.stringify(satoshiPerByteBucketMaxima)};

		$(document).ready(function() {
			loadMempool(txidChunks, 25, txidChunks.length * 25);
		});

		function loadMempool(txidChunks, chunkSize, count) {
			var chunkStrs = [];
			
			for (var i = 0; i < txidChunks.length; i++) {
				var txidChunk = txidChunks[i];

				var chunkStr = "";

				for (var j = 0; j < txidChunk.length; j++) {
					if (j > 0) {
						chunkStr += ",";
					}

					chunkStr += txidChunk[j];
				}

				chunkStrs.push(chunkStr);
			}

			//alert(JSON.stringify(chunks));

			var results = [];

			var statusCallback = function(chunkIndexDone, chunkCount) {
				//console.log("Done: " + Math.min(((chunkIndexDone + 1) * chunkSize), count) + " of " + count);

				var wPercent = `${parseInt(100 * (chunkIndexDone + 1) / parseFloat(chunkCount))}%`;
				
				$("#data-progress").css("width", wPercent);
				$("#progress-text").text(`${Math.min(((chunkIndexDone + 1) * chunkSize), count).toLocaleString()} of ${count.toLocaleString()} (${wPercent})`);
			};

			var finishedCallback = function() {
				var summary = summarizeData(results);

				var feeRateGraphData = buildFeeRateGraphData(summary);
				var txSizeGraphData = buildTxSizeGraphData(summary);
				var txAgeGraphData = buildTxAgeGraphData(summary);

				//console.log(JSON.stringify(summary));

				$("#tx-count").text(summary.count.toLocaleString());
				$("#mem-usage").text(summary.totalBytes.toLocaleString());
				$("#total-fees").text(summary.totalFees);
				
				if (summary.count == 0) {
					$("#avg-fee").text("-");
					$("#avg-fee-rate").text("-");

					$("#detail-charts-and-data").hide();

					$("#main-content").show();
					$("#progress-wrapper").hide();

					return;
				}
				
				$("#avg-fee").text(summary.averageFee);
				$("#avg-fee-rate").text(summary.averageFeePerByte);

				$.ajax({
					url: `./api/utils/formatLargeNumber/${summary.totalBytes},2`

				}).done(function(result) {
					$("#mem-usage").html(`<span>${result[0]} <small>${result[1].abbreviation}B</small></span>`);
				});

				updateCurrencyValue($("#total-fees"), summary.totalFees);
				updateCurrencyValue($("#avg-fee"), summary.averageFee);

				updateFeeRateValue($("#avg-fee-rate"), summary.averageFeePerByte, 2);


				//$("#summary-json").text(JSON.stringify(summary, null, 4));


				// fee rate chart
				var ctx1 = document.getElementById("mempoolBarChart").getContext('2d');
				var mempoolBarChart = new Chart(ctx1, {
					type: 'bar',
					data: {
						labels: feeRateGraphData.feeBucketLabels,
						datasets: [{
							data: feeRateGraphData.feeBucketTxCounts,
							backgroundColor: feeRateGraphData.bgColors
						}]
					},
					options: {
						legend: {
							display: false
						},
						scales: {
							yAxes: [{
								ticks: {
									beginAtZero:true
								}
							}]
						}
					}
				});

				// tx size chart
				var ctx2 = document.getElementById("txSizesBarChart").getContext('2d');
				var txSizesBarChart = new Chart(ctx2, {
					type: 'bar',
					data: {
						labels: txSizeGraphData.sizeBucketLabels,
						datasets: [{
							data: txSizeGraphData.sizeBucketTxCounts,
							backgroundColor: txSizeGraphData.bgColors
						}]
					},
					options: {
						legend: {
							display: false
						},
						scales: {
							yAxes: [{
								ticks: {
									beginAtZero:true
								}
							}]
						}
					}
				});

				// tx age chart
				var ctx3 = document.getElementById("txAgesBarChart").getContext('2d');
				var txSizesBarChart = new Chart(ctx3, {
					type: 'bar',
					data: {
						labels: txAgeGraphData.ageBucketLabels,
						datasets: [{
							data: txAgeGraphData.ageBucketTxCounts,
							backgroundColor: txAgeGraphData.bgColors
						}]
					},
					options: {
						legend: {
							display: false
						},
						scales: {
							yAxes: [{
								ticks: {
									beginAtZero:true
								}
							}]
						}
					}
				});


				// fee rate table
				for (var i = 0; i < summary.satoshiPerByteBuckets.length; i++) {
					var item = summary.satoshiPerByteBuckets[i];

					var row = $("#fee-rate-table-row-prototype").clone();
					row.attr("id", null);
					row.addClass("fee-rate-table-row");

					row.find(".data-label").text(summary.satoshiPerByteBucketLabels[i]);
					row.find(".data-count").text(item.count.toLocaleString());
					row.find(".data-total-fees").text(item.count > 0 ? item.totalFees : "-");
					row.find(".data-avg-fee").text(item.count > 0 ? item.totalFees / item.count : "-");
					row.find(".data-fee-rate").text("-");

					if (item.count > 0) {
						updateCurrencyValue(row.find(".data-total-fees"), item.totalFees);
						updateCurrencyValue(row.find(".data-avg-fee"), item.totalFees / item.count);

						updateFeeRateValue(row.find(".data-fee-rate"), item.totalFees / item.totalBytes, 2);
					}

					row.show();

					$("#fee-rate-table-body").append(row);
				}
				

				$("#main-content").show();
				$("#progress-wrapper").hide();
			};

			getTxData(results, chunkStrs, 0, statusCallback, finishedCallback);
		}

		function getTxData(results, chunkStrs, chunkIndex, statusCallback, finishedCallback) {
			if (chunkIndex > chunkStrs.length - 1) {
				finishedCallback();

				return;
			}

			var url = `./api/mempool-txs/${chunkStrs[chunkIndex]}`;
			
			//console.log(url);

			$.ajax({
				url: url

			}).done(function(result) {
				for (var i = 0; i < result.length; i++) {
					results.push(result[i]);
				}

				statusCallback(chunkIndex, chunkStrs.length);
				
				getTxData(results, chunkStrs, chunkIndex + 1, statusCallback, finishedCallback);
			});
		}

		function buildFeeRateGraphData(summary) {
			var feeBucketLabels = [("[0 - " + summary["satoshiPerByteBucketMaxima"][0] + ")")];
			
			for (var i = 0; i < summary["satoshiPerByteBuckets"].length; i++) {
				var item = summary["satoshiPerByteBuckets"][i];
				if (i > 0 && i < summary["satoshiPerByteBuckets"].length - 1) {
					feeBucketLabels.push(("[" + summary["satoshiPerByteBucketMaxima"][i - 1] + " - " + summary["satoshiPerByteBucketMaxima"][i] + ")"));
				}
			}

			feeBucketLabels.push((summary.satoshiPerByteBucketMaxima[summary.satoshiPerByteBucketMaxima.length - 1] + "+"));
				
			var feeBucketTxCounts = summary["satoshiPerByteBucketCounts"];
			var totalfeeBuckets = summary["satoshiPerByteBucketTotalFees"];

			var graphData = {feeBucketLabels:[], bgColors:[], feeBucketTxCounts:feeBucketTxCounts};

			for (var i = 0; i < feeBucketLabels.length; i++) {
				var feeBucketLabel = feeBucketLabels[i];
				var percentTx = Math.round(100 * feeBucketTxCounts[i] / summary.count).toLocaleString();
				
				graphData.feeBucketLabels.push([feeBucketLabel, `${feeBucketTxCounts[i]} tx (${percentTx}%)`]);
				graphData.bgColors.push(`hsl(${(333 * i / feeBucketLabels.length)}, 100%, 50%)`);
			}

			return graphData;
		}

		function buildTxSizeGraphData(summary) {
			var sizeBucketLabels = [];
			var bgColors = [];

			for (var i = 0; i < summary.sizeBucketLabels.length; i++) {
				var sizeBucketLabel = summary.sizeBucketLabels[i];
				var percentTx = Math.round(100 * summary.sizeBucketTxCounts[i] / summary.count).toLocaleString();

				sizeBucketLabels.push([`${sizeBucketLabel} bytes`, `${summary.sizeBucketTxCounts[i]} tx (${percentTx}%)`]);
				bgColors.push(`hsl(${(333 * i / summary.sizeBucketLabels.length)}, 100%, 50%)`);
			}

			return {
				sizeBucketLabels: sizeBucketLabels,
				bgColors: bgColors,
				sizeBucketTxCounts: summary.sizeBucketTxCounts
			};
		}

		function buildTxAgeGraphData(summary) {
			var ageBucketLabels = [];
			var bgColors = [];

			for (var i = 0; i < summary.ageBucketLabels.length; i++) {
				var ageBucketLabel = summary.ageBucketLabels[i];
				var percentTx = Math.round(100 * summary.ageBucketTxCounts[i] / summary.count).toLocaleString();

				ageBucketLabels.push([`${ageBucketLabel}`, `${summary.ageBucketTxCounts[i]} tx (${percentTx}%)`]);
				bgColors.push(`hsl(${(333 * i / summary.ageBucketLabels.length)}, 100%, 50%)`);
			}

			return {
				ageBucketLabels: ageBucketLabels,
				bgColors: bgColors,
				ageBucketTxCounts: summary.ageBucketTxCounts
			};
		}

		function summarizeData(rawdata) {
			var summary = [];

			var maxFee = 0;
			var maxFeePerByte = 0;
			var maxAge = 0;
			var maxSize = 0;
			var ages = [];
			var sizes = [];

			for (var i = 0; i < rawdata.length; i++) {
				var txMempoolInfo = rawdata[i].entry;

				var fee = txMempoolInfo.modifiedfee;
				var size = txMempoolInfo.vsize ? txMempoolInfo.vsize : txMempoolInfo.size;
				var feePerByte = txMempoolInfo.modifiedfee / size;
				var age = Date.now() / 1000 - txMempoolInfo.time;

				if (fee > maxFee) {
					maxFee = txMempoolInfo.modifiedfee;
				}

				if (feePerByte > maxFeePerByte) {
					maxFeePerByte = txMempoolInfo.modifiedfee / size;
				}

				ages.push({age:age, txid:"abc"});
				sizes.push({size:size, txid:"abc"});

				if (age > maxAge) {
					maxAge = age;
				}

				if (size > maxSize) {
					maxSize = size;
				}
			}

			ages.sort(function(a, b) {
				if (a.age != b.age) {
					return b.age - a.age;

				} else {
					return a.txid.localeCompare(b.txid);
				}
			});

			sizes.sort(function(a, b) {
				if (a.size != b.size) {
					return b.size - a.size;

				} else {
					return a.txid.localeCompare(b.txid);
				}
			});

			maxSize = 2000;

			var bucketCount = satoshiPerByteBucketMaxima.length + 1;

			var satoshiPerByteBuckets = [];
			var satoshiPerByteBucketLabels = [];

			satoshiPerByteBucketLabels[0] = ("[0 - " + satoshiPerByteBucketMaxima[0] + ")");
			for (var i = 0; i < bucketCount; i++) {
				satoshiPerByteBuckets[i] = {"count":0, "totalFees":0, "totalBytes":0};

				if (i > 0 && i < bucketCount - 1) {
					satoshiPerByteBucketLabels[i] = ("[" + satoshiPerByteBucketMaxima[i - 1] + " - " + satoshiPerByteBucketMaxima[i] + ")");
				}
			}

			var ageBucketCount = 150;
			var ageBucketTxCounts = [];
			var ageBucketLabels = [];

			var sizeBucketCount = 150;
			var sizeBucketTxCounts = [];
			var sizeBucketLabels = [];

			for (var i = 0; i < ageBucketCount; i++) {
				var rangeMin = i * maxAge / ageBucketCount;
				var rangeMax = (i + 1) * maxAge / ageBucketCount;

				ageBucketTxCounts.push(0);

				if (maxAge > 600) {
					var rangeMinutesMin = new Decimal(rangeMin / 60).toFixed(1);
					var rangeMinutesMax = new Decimal(rangeMax / 60).toFixed(1);

					ageBucketLabels.push(rangeMinutesMin + " - " + rangeMinutesMax + " min");

				} else {
					ageBucketLabels.push(parseInt(rangeMin) + " - " + parseInt(rangeMax) + " sec");
				}
			}

			for (var i = 0; i < sizeBucketCount; i++) {
				sizeBucketTxCounts.push(0);

				if (i == sizeBucketCount - 1) {
					sizeBucketLabels.push(parseInt(i * maxSize / sizeBucketCount) + "+");

				} else {
					sizeBucketLabels.push(parseInt(i * maxSize / sizeBucketCount) + " - " + parseInt((i + 1) * maxSize / sizeBucketCount));
				}
			}

			satoshiPerByteBucketLabels[bucketCount - 1] = (satoshiPerByteBucketMaxima[satoshiPerByteBucketMaxima.length - 1] + "+");

			var summary = {
				"count":0,
				"totalFees":0,
				"totalBytes":0,
				"satoshiPerByteBuckets":satoshiPerByteBuckets,
				"satoshiPerByteBucketLabels":satoshiPerByteBucketLabels,
				"ageBucketTxCounts":ageBucketTxCounts,
				"ageBucketLabels":ageBucketLabels,
				"sizeBucketTxCounts":sizeBucketTxCounts,
				"sizeBucketLabels":sizeBucketLabels
			};

			for (var x = 0; x < rawdata.length; x++) {
				var txMempoolInfo = rawdata[x].entry;
				var fee = txMempoolInfo.modifiedfee;
				var size = txMempoolInfo.vsize ? txMempoolInfo.vsize : txMempoolInfo.size;
				var feePerByte = txMempoolInfo.modifiedfee / size;
				var satoshiPerByte = feePerByte * 100000000; // TODO: magic number - replace with coinConfig.baseCurrencyUnit.multiplier
				var age = Date.now() / 1000 - txMempoolInfo.time;

				var addedToBucket = false;
				for (var i = 0; i < satoshiPerByteBucketMaxima.length; i++) {
					if (satoshiPerByteBucketMaxima[i] > satoshiPerByte) {
						satoshiPerByteBuckets[i]["count"]++;
						satoshiPerByteBuckets[i]["totalFees"] += fee;
						satoshiPerByteBuckets[i]["totalBytes"] += size;

						addedToBucket = true;

						break;
					}
				}

				if (!addedToBucket) {
					satoshiPerByteBuckets[bucketCount - 1]["count"]++;
					satoshiPerByteBuckets[bucketCount - 1]["totalFees"] += fee;
					satoshiPerByteBuckets[bucketCount - 1]["totalBytes"] += size;
				}

				summary["count"]++;
				summary["totalFees"] += txMempoolInfo.modifiedfee;
				summary["totalBytes"] += size;

				var ageBucketIndex = Math.min(ageBucketCount - 1, parseInt(age / (maxAge / ageBucketCount)));
				var sizeBucketIndex = Math.min(sizeBucketCount - 1, parseInt(size / (maxSize / sizeBucketCount)));

				ageBucketTxCounts[ageBucketIndex]++;
				sizeBucketTxCounts[sizeBucketIndex]++;
			}

			summary["averageFee"] = summary["totalFees"] / summary["count"];
			summary["averageFeePerByte"] = summary["totalFees"] / summary["totalBytes"];

			summary["satoshiPerByteBucketMaxima"] = satoshiPerByteBucketMaxima;
			summary["satoshiPerByteBucketCounts"] = [];
			summary["satoshiPerByteBucketTotalFees"] = [];

			for (var i = 0; i < bucketCount; i++) {
				summary["satoshiPerByteBucketCounts"].push(summary["satoshiPerByteBuckets"][i]["count"]);
				summary["satoshiPerByteBucketTotalFees"].push(summary["satoshiPerByteBuckets"][i]["totalFees"]);
			}

			return summary;
		}
